import {TinyEmitter} from "tiny-emitter";
import {AppTinyEmitter} from "../AkabaVendor/AppTinyEmitter";
import {TableCardList} from "./TableCardList";

export namespace IndexedDBTable {
    export interface IEvtArg extends AppTinyEmitter.IBaseEvtMap {
        install_complete: [
            IndexedDBTable.IEvtInstallComplete
        ],
        install_error: [
            IndexedDBTable.IEvtInstallError,
        ],
        transaction_error: [
            IndexedDBTable.IEvtTransactionError,
        ],
        transaction_complete: [
            IndexedDBTable.IEvtTransactionComplete,
        ],
        travel_cursor: [
            IndexedDBTable.IEvtOpenCursorSuccess<any>
        ],
    }
    export interface IEvtTargetResult<T> extends EventTarget {
        result: T,
    }
    export interface IEvtWithTargetResult<T> extends Event {
        target: IEvtTargetResult<T>,
    }
    export interface IEvtInstallComplete extends Event {
    }
    export interface IEvtInstallError extends Event {
    }
    export interface IEvtTransactionError extends Event {
    }
    export interface IEvtTransactionComplete extends Event {
    }
    export interface IEvtOpenCursorResultValue<T> extends IDBCursorWithValue {
        value: T,
    }
    export interface IEvtOpenCursorSuccess<T> extends Event {
        target: IDBRequest<IEvtOpenCursorResultValue<T> | null>
    }

    export interface BaseItem {
    }
    export type TGetStatus = "create" | "get";
    export type TItemFilter<T> = (item: T) => boolean | undefined;
}

export abstract class IndexedDBTable<T extends IndexedDBTable.BaseItem = {}> extends AppTinyEmitter<IndexedDBTable.IEvtArg>{
    public readonly db: IDBDatabase;
    public abstract readonly storeName: string;
    public evtEmitterId = "database object store";
    private isInstalled: boolean = false;

    protected constructor(db: IDBDatabase) {
        super();
        this.db = db;
    }
    public startInstall(): this {
        if (this.isInstalled) {
            return this;
        }
        const objectStore = this.db.createObjectStore(this.storeName, this.getTableOption());
        this.install(objectStore);
        objectStore.transaction?.addEventListener("complete", (evt: Event) => {
            this.emit("install_complete", evt);
            this.isInstalled = false;
        });
        objectStore.transaction?.addEventListener("error", (evt: Event) => {
            this.emit("install_error", evt);
        });
        return this;
    }
    protected getTableOption(): IDBObjectStoreParameters | undefined {
        return undefined;
    }
    protected install(objectStore: IDBObjectStore):void {};

    public getIDBObjectStore(): IDBObjectStore {
        const transaction = this.getTransaction();
        return transaction.objectStore(this.storeName);
    }

    public getTransaction() {
        const transaction = this.db.transaction([this.storeName], "readwrite");
        transaction.addEventListener("error", (err) => {
           this.emit("transaction_error", err);
        });
        transaction.addEventListener("complete", evt => {
           this.emit("transaction_complete", evt);
        });
        return transaction;
    }

    public getList(filter?: IndexedDBTable.TItemFilter<T>): Promise<T[]> {
        return new Promise((resolve, reject) => {
            const list: T[] = [];
            const cursor = this.getIDBObjectStore().openCursor();
            cursor.addEventListener("success", (evt: Event) => {
                const cursorSuccessEvt: IndexedDBTable.IEvtOpenCursorSuccess<T> = evt as any;
                const cursor = cursorSuccessEvt.target.result;
                if (cursor) {
                    this.emit("travel_cursor", cursorSuccessEvt);
                    const val = cursor.value;
                    if (
                        !filter ||
                        (filter && filter(val))
                    ) {
                        list.push(val);
                    }
                    cursor.continue();
                } else {
                    resolve(list);
                }
            });
            cursor.addEventListener("error", (err) => {
                reject(err);
            });
        })
    }

    public add(item: T): Promise<void> {
        return new Promise((resolve, reject) => {
           this.on("transaction_error", (err) => reject(err));
           this.on("transaction_complete", () => {
               resolve();
           });
           this.getIDBObjectStore().add(item);
        });
    }
}
